/*++

Copyright (c) 2005 - 2006, Intel Corporation                                                         
All rights reserved. This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 


Module Name:

  Ip4Icmp.c

Abstract:

--*/

#include "Ip4Impl.h"

IP4_ICMP_CLASS 
mIcmpClass[] = {
  {ICMP_ECHO_REPLY,         ICMP_QUERY_MESSAGE  },
  {1,                       ICMP_INVALID_MESSAGE},
  {2,                       ICMP_INVALID_MESSAGE},
  {ICMP_DEST_UNREACHABLE,   ICMP_ERROR_MESSAGE  },
  {ICMP_SOURCE_QUENCH,      ICMP_ERROR_MESSAGE  },       
  {ICMP_REDIRECT,           ICMP_ERROR_MESSAGE  },            
  {6,                       ICMP_INVALID_MESSAGE},
  {7,                       ICMP_INVALID_MESSAGE},
  {ICMP_ECHO_REQUEST,       ICMP_QUERY_MESSAGE  },        
  {9,                       ICMP_INVALID_MESSAGE},
  {10,                      ICMP_INVALID_MESSAGE},
  {ICMP_TIME_EXCEEDED,      ICMP_ERROR_MESSAGE  },       
  {ICMP_PARAMETER_PROBLEM,  ICMP_ERROR_MESSAGE  },   
  {ICMP_TIMESTAMP ,         ICMP_QUERY_MESSAGE  },            
  {14,                      ICMP_INVALID_MESSAGE}, 
  {ICMP_INFO_REQUEST ,      ICMP_QUERY_MESSAGE  },         
  {ICMP_INFO_REPLY ,        ICMP_QUERY_MESSAGE  },           
};

EFI_IP4_ICMP_TYPE 
mIp4SupportedIcmp [23] = {
  {ICMP_ECHO_REPLY,        ICMP_DEFAULT_CODE      },
    
  {ICMP_DEST_UNREACHABLE,  ICMP_NET_UNREACHABLE   },
  {ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNREACHABLE  },
  {ICMP_DEST_UNREACHABLE,  ICMP_PROTO_UNREACHABLE },
  {ICMP_DEST_UNREACHABLE,  ICMP_PORT_UNREACHABLE  },
  {ICMP_DEST_UNREACHABLE,  ICMP_FRAGMENT_FAILED   },
  {ICMP_DEST_UNREACHABLE,  ICMP_SOURCEROUTE_FAILED},
  {ICMP_DEST_UNREACHABLE,  ICMP_NET_UNKNOWN       },
  {ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNKNOWN      },
  {ICMP_DEST_UNREACHABLE,  ICMP_SOURCE_ISOLATED   },
  {ICMP_DEST_UNREACHABLE,  ICMP_NET_PROHIBITED    },
  {ICMP_DEST_UNREACHABLE,  ICMP_HOST_PROHIBITED   },
  {ICMP_DEST_UNREACHABLE,  ICMP_NET_UNREACHABLE_TOS },
  {ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNREACHABLE_TOS},
    
  {ICMP_SOURCE_QUENCH,     ICMP_DEFAULT_CODE      },
  
  {ICMP_REDIRECT,          ICMP_NET_REDIRECT      },
  {ICMP_REDIRECT,          ICMP_HOST_REDIRECT     },
  {ICMP_REDIRECT,          ICMP_NET_TOS_REDIRECT  },
  {ICMP_REDIRECT,          ICMP_HOST_TOS_REDIRECT },

  {ICMP_ECHO_REQUEST,      ICMP_DEFAULT_CODE      },
    
  {ICMP_TIME_EXCEEDED,     ICMP_TIMEOUT_IN_TRANSIT},
  {ICMP_TIME_EXCEEDED,     ICMp_TIMEOUT_REASSEMBLE},
  
  {ICMP_PARAMETER_PROBLEM, ICMP_DEFAULT_CODE      },
};


STATIC
EFI_STATUS
Ip4ProcessIcmpRedirect (
  IN IP4_SERVICE            *IpSb,
  IN IP4_HEAD               *Head,
  IN NET_BUF                *Packet,
  IN IP4_ICMP_ERROR_HEAD    *Icmp
  )
/*++

Routine Description:

  Process the ICMP redirect. Find the instance then update
  its route cache.
  
  All kinds of redirect is treated as host redirect as 
  specified by RFC1122 3.3.1.2:
    "Since the subnet mask appropriate to the destination
    address is generally not known, a Network Redirect 
    message SHOULD be treated identically to a Host Redirect 
    message;"

Arguments:

  IpSb    - The IP4 service binding instance that received the packet
  Head    - The IP head of the received ICMPpacket.
  Packet  - The content of the ICMP redirect packet with IP head removed.
  Icmp    - The buffer to store the ICMP error message if something is wrong.

Returns:

  EFI_INVALID_PARAMETER - The parameter is invalid
  EFI_SUCCESS           - Successfully updated the route caches

--*/
{
  NET_LIST_ENTRY            *Entry;
  IP4_PROTOCOL              *Ip4Instance;
  IP4_ROUTE_CACHE_ENTRY     *CacheEntry;
  IP4_INTERFACE             *IpIf;
  IP4_ADDR                  Gateway;

  //
  // Find the interface whose IP address is the source of the 
  // orgianl IP packet. 
  //
  IpIf    = Ip4FindInterface (IpSb, NTOHL (Icmp->IpHead.Src));
  Gateway = NTOHL (Icmp->Fourth);

  //
  // discard the packet if the new gateway address it specifies 
  // is not on the same connected net through which the Redirect 
  // arrived. (RFC1122 3.2.2.2).
  //
  if ((IpIf == NULL) || !IP4_NET_EQUAL (Gateway, IpIf->Ip, IpIf->SubnetMask)) {   
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  //
  // Update each IP child's route cache on the interface.
  //
  NET_LIST_FOR_EACH (Entry, &IpIf->IpInstances) {
    Ip4Instance = NET_LIST_USER_STRUCT (Entry, IP4_PROTOCOL, AddrLink);

    if (Ip4Instance->RouteTable == NULL) {
      continue;
    }

    CacheEntry = Ip4FindRouteCache (
                   Ip4Instance->RouteTable,
                   NTOHL (Icmp->IpHead.Dst),
                   NTOHL (Icmp->IpHead.Src)
                   );

    //
    // Only update the route cache's gateway if the source of the 
    // Redirect is the current first-hop gateway
    //
    if ((CacheEntry != NULL) && (NTOHL (Head->Src) == CacheEntry->NextHop)) {
      CacheEntry->NextHop = Gateway;
    }
  }

  NetbufFree (Packet);
  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
Ip4ProcessIcmpError (
  IN IP4_SERVICE            *IpSb,
  IN IP4_HEAD               *Head,
  IN NET_BUF                *Packet
  )
/*++

Routine Description:

  Process the ICMP error packet. If it is an ICMP redirect packet,
  update call Ip4ProcessIcmpRedirect to update the IP instance's 
  route cache, otherwise, deliver the packet to upper layer.

Arguments:

  IpSb    - The IP service that received the packet.
  Head    - The IP head of the ICMP error packet
  Packet  - The content of the ICMP error with IP head removed.

Returns:

  EFI_INVALID_PARAMETER - The packet is invalid
  Others                - Failed to process the packet.
  EFI_SUCCESS           - The ICMP error is processed successfully.

--*/
{
  IP4_ICMP_ERROR_HEAD       Icmp;

  if (Packet->TotalSize < sizeof (Icmp)) {
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);

  //
  // If it is an ICMP redirect error, update the route cache 
  // as RFC1122. Otherwise, demultiplex it to IP instances.
  //
  if (Icmp.Head.Type == ICMP_REDIRECT) {
    return Ip4ProcessIcmpRedirect (IpSb, Head, Packet, &Icmp);
  }

  IP4_GET_CLIP_INFO (Packet)->Status = EFI_ICMP_ERROR;
  return Ip4Demultiplex (IpSb, Head, Packet);
}

EFI_STATUS
Ip4IcmpReplyEcho (
  IN IP4_SERVICE            *IpSb,
  IN IP4_HEAD               *Head,
  IN NET_BUF                *Packet
  )
/*++

Routine Description:

  Replay an ICMP echo request. 

Arguments:

  IpSb    - The IP service that receivd the packet 
  Head    - The IP head of the ICMP error packet
  Packet  - The content of the ICMP error with IP head removed.

Returns:

  EFI_OUT_OF_RESOURCES - Failed to allocate resource. 
  EFI_SUCCESS          - The ICMP Echo request is successfully answered.
  Others               - Failed to answer the ICMP echo request.

--*/
{
  IP4_ICMP_QUERY_HEAD       *Icmp;
  NET_BUF                   *Data;
  EFI_STATUS                Status;
  IP4_HEAD                  ReplyHead;

  //
  // make a copy the packet, it is really a bad idea to 
  // send the MNP's buffer back to MNP.
  //
  Data = NetbufDuplicate (Packet, NULL, IP4_MAX_HEADLEN);

  if (Data == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }
  
  //
  // Change the ICMP type to echo reply, exchange the source
  // and destination, then send it. The source is updated to
  // use specific destination. See RFC1122. SRR/RR option
  // update is omitted.
  //
  Icmp                = (IP4_ICMP_QUERY_HEAD *) NetbufGetByte (Data, 0, NULL);
  Icmp->Head.Type     = ICMP_ECHO_REPLY;
  Icmp->Head.Checksum = 0;
  Icmp->Head.Checksum = ~NetblockChecksum ((UINT8 *) Icmp, Data->TotalSize);

  ReplyHead.Tos       = 0;
  ReplyHead.Fragment  = 0;
  ReplyHead.Ttl       = 64;
  ReplyHead.Protocol  = IP4_PROTO_ICMP;
  ReplyHead.Src       = 0;
  
  //
  // Ip4Output will select a source for us
  //
  ReplyHead.Dst = Head->Src;

  Status = Ip4Output (
             IpSb,
             NULL,
             Data,
             &ReplyHead,
             NULL,
             0,
             IP4_ALLZERO_ADDRESS,
             Ip4SysPacketSent,
             NULL
             );

ON_EXIT:
  NetbufFree (Packet);
  return Status;
}

EFI_STATUS
Ip4ProcessIcmpQuery (
  IN IP4_SERVICE            *IpSb,
  IN IP4_HEAD               *Head,
  IN NET_BUF                *Packet
  )
/*++

Routine Description:

  Process the ICMP query message. If it is an ICMP echo 
  request, answer it. Otherwise deliver it to upper layer.

Arguments:

  IpSb    - The IP service that receivd the packet 
  Head    - The IP head of the ICMP query packet
  Packet  - The content of the ICMP query with IP head removed.

Returns:

  EFI_INVALID_PARAMETER - The packet is invalid
  EFI_SUCCESS           - The ICMP query message is processed
  
--*/
{
  IP4_ICMP_QUERY_HEAD       Icmp;

  if (Packet->TotalSize < sizeof (Icmp)) {
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);

  if (Icmp.Head.Type == ICMP_ECHO_REQUEST) {
    return Ip4IcmpReplyEcho (IpSb, Head, Packet);
  }

  return Ip4Demultiplex (IpSb, Head, Packet);
}

EFI_STATUS
Ip4IcmpHandle (
  IN IP4_SERVICE            *IpSb,
  IN IP4_HEAD               *Head,
  IN NET_BUF                *Packet
  )
/*++

Routine Description:

  Handle the ICMP packet. First validate the message format,
  then according to the message types, process it as query or
  error packet.

Arguments:

  IpSb    - The IP service that receivd the packet 
  Head    - The IP head of the ICMP query packet
  Packet  - The content of the ICMP query with IP head removed.

Returns:

  EFI_INVALID_PARAMETER - The packet is malformated.
  EFI_SUCCESS           - The ICMP message is successfully processed.

--*/
{
  IP4_ICMP_HEAD             Icmp;
  UINT16                    Checksum;

  if (Packet->TotalSize < sizeof (Icmp)) {
    goto DROP;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);

  if (Icmp.Type > ICMP_TYPE_MAX) {
    goto DROP;
  }

  Checksum = ~NetbufChecksum (Packet);
  if ((Icmp.Checksum != 0) && (Checksum != 0)) {
    goto DROP;
  }

  if (mIcmpClass[Icmp.Type].IcmpClass == ICMP_ERROR_MESSAGE) {
    return Ip4ProcessIcmpError (IpSb, Head, Packet);

  } else if (mIcmpClass[Icmp.Type].IcmpClass == ICMP_QUERY_MESSAGE) {
    return Ip4ProcessIcmpQuery (IpSb, Head, Packet);

  }

DROP:
  NetbufFree (Packet);
  return EFI_INVALID_PARAMETER;
}
